package hexapawn.io;

import java.awt.AWTException;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.SwingUtilities;

public class UIOManager implements KeyListener, MouseListener, MouseMotionListener, MouseWheelListener{
	
	public static final Cursor INVIS_CURSOR = Toolkit.getDefaultToolkit().createCustomCursor(Toolkit.getDefaultToolkit().getImage(""), new Point(0, 0), "invis_cursor");
	
	//Mouse Codes
	public static final int MOUSE_MOVE_LEFT = 0;
	public static final int MOUSE_MOVE_RIGHT = 1;
	public static final int MOUSE_MOVE_UP = 2;
	public static final int MOUSE_MOVE_DOWN = 3;
	public static final int MOUSE_WHEEL_UP = 4;
	public static final int MOUSE_WHEEL_DOWN = 5;
	public static final int MOUSE_BUTTON_1 = 6;
	public static final int MOUSE_BUTTON_2 = 7;
	public static final int MOUSE_BUTTON_3 = 8;
	
	private static final int NUM_MOUSE_CODES = 9;
	/*
	 * Most key codes that are commonly used are under 600
	 */
	private static final int NUM_KEY_CODES = 600;
	
	private UIOAction[] keyActions = new UIOAction[NUM_KEY_CODES];
	private UIOAction[] mouseActions = new UIOAction[NUM_MOUSE_CODES];
	//Control Variables
	private Point mouseLoc;
	private Point center;
	private Component comp;
	private Robot robot;
	private boolean isRecentering;
	
	/**
	 * CONSTRUCTOR - Create a new UIOManager (User-Input-Ouput-Manager) that listens
	 * to inputs from the specified component.
	 * 
	 * @param comp - The component to listen input from.
	 */
	public UIOManager(Component comp){
		this.comp = comp;
		mouseLoc = new Point();
		center = new Point();
		//Register Key and Mouse Listeners
		comp.addKeyListener(this);
		comp.addMouseListener(this);
		comp.addMouseMotionListener(this);
		comp.addMouseWheelListener(this);
		//Allow input of TAB key or others normally
		comp.setFocusTraversalKeysEnabled(false);
	}
	
	public void setCursor(Cursor c){
		comp.setCursor(c);
	}
	
	public void setRelativeMouseMode(boolean mode){
		if(mode == isRelativeMouseMode()) return;
		if(mode){
			try{
				robot = new Robot();
				recenterMouse();
			}catch(AWTException e){
				System.err.println("Could not create Robot!");
				robot = null;
			}
		}else robot = null;
	}
	
	public void maptoKey(UIOAction action, int code){
		keyActions[code] = action;
	}
	
	public void mapToMouse(UIOAction action, int code){
		mouseActions[code] = action;
	}
	
	public void clearMap(UIOAction action){
		int i;
		for(i=0; i<keyActions.length; i++)
			if(keyActions[i] == action)
				keyActions[i] = null;
		for(i=0; i<mouseActions.length; i++)
			if(mouseActions[i] == action)
				mouseActions[i] = null;
		action.reset();
	}
	
	public List<String> getMaps(UIOAction action){
		List<String> list = new ArrayList<String>();
		int i;
		for(i=0; i<keyActions.length;i++)
			if(keyActions[i] == action)
				list.add(getKeyName(i));
		for(i=0; i<mouseActions.length;i++)
			if(mouseActions[i] == action)
				list.add(getMouseName(i));		
		return list;
	}
	
    public void resetAllGameActions() {
    	int i;
        for (i=0; i<keyActions.length; i++)
            if (keyActions[i] != null)
                keyActions[i].reset();
        for (i=0; i<mouseActions.length; i++)
            if (mouseActions[i] != null)
                mouseActions[i].reset();
    }
	
	public boolean isRelativeMouseMode(){
		return (robot != null);
	}
	
	public int getMouseX(){
		return mouseLoc.x;
	}
	
	public int getMouseY(){
		return mouseLoc.y;
	}
	
	@Override
	public void mouseWheelMoved(MouseWheelEvent e) {
		mouseHelper(MOUSE_WHEEL_UP, MOUSE_WHEEL_DOWN, e.getWheelRotation());
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		mouseMoved(e);
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		if(isRecentering && center.x == e.getX() && center.y == e.getY())
			isRecentering = false;
		else{
			int dx = e.getX() - mouseLoc.x;
			int dy = e.getY() - mouseLoc.y;
			mouseHelper(MOUSE_MOVE_LEFT, MOUSE_MOVE_RIGHT, dx);
			mouseHelper(MOUSE_MOVE_UP, MOUSE_MOVE_DOWN, dy);
			if(isRelativeMouseMode()) recenterMouse();
		}
		mouseLoc.x = e.getX();
		mouseLoc.y = e.getY();
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		//Do nothing
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		mouseMoved(e);
	}

	@Override
	public void mouseExited(MouseEvent e) {
		mouseMoved(e);
	}

	@Override
	public void mousePressed(MouseEvent e) {
		UIOAction action = getMouseButtonAction(e);
		if(action != null) action.press();
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		UIOAction action = getMouseButtonAction(e);
		if(action != null) action.release();
	}

	@Override
	public void keyPressed(KeyEvent e) {
		UIOAction action = getKeyAction(e);
		if(action != null) action.press();
		//Making sure the key isn't processed for other things
		e.consume();
	}

	@Override
	public void keyReleased(KeyEvent e) {
		UIOAction action = getKeyAction(e);
		if(action != null) action.release();
		e.consume();
	}

	@Override
	public void keyTyped(KeyEvent e) {
		//We don't care about this event
		e.consume();
	}
	
	public static String getKeyName(int keyCode){
		return KeyEvent.getKeyText(keyCode);
	}
	
	public static String getMouseName(int mouseCode){
		switch(mouseCode){
		case MOUSE_MOVE_LEFT: return "Mouse Left";
        case MOUSE_MOVE_RIGHT: return "Mouse Right";
        case MOUSE_MOVE_UP: return "Mouse Up";
        case MOUSE_MOVE_DOWN: return "Mouse Down";
        case MOUSE_WHEEL_UP: return "Mouse Wheel Up";
        case MOUSE_WHEEL_DOWN: return "Mouse Wheel Down";
        case MOUSE_BUTTON_1: return "Mouse Button 1";
        case MOUSE_BUTTON_2: return "Mouse Button 2";
        case MOUSE_BUTTON_3: return "Mouse Button 3";
        default: return "Unknown mouse code " + mouseCode;
		}
	}
	
	public static int getMouseButtonCode(MouseEvent e) {
		switch (e.getButton()) {
		case MouseEvent.BUTTON1:
			return MOUSE_BUTTON_1;
		case MouseEvent.BUTTON2:
			return MOUSE_BUTTON_2;
		case MouseEvent.BUTTON3:
			return MOUSE_BUTTON_3;
		default:
			return -1;
		}
	}
	
	private UIOAction getKeyAction(KeyEvent e) {
		int keyCode = e.getKeyCode();
		if (keyCode < keyActions.length)
			return keyActions[keyCode];
		else return null;
	}

	
	private UIOAction getMouseButtonAction(MouseEvent e) {
		int mouseCode = getMouseButtonCode(e);
		if (mouseCode != -1)
			return mouseActions[mouseCode];
		else return null;
	}
	
	private synchronized void recenterMouse(){
		if(robot != null && comp.isShowing()){
			center.x = comp.getWidth()/2;
			center.y = comp.getHeight()/2;
			SwingUtilities.convertPointToScreen(center, comp);
			isRecentering = true;
			robot.mouseMove(center.x, center.y);
		}
	}
	
	private void mouseHelper(int codeNeg, int codePos, int amount){
		UIOAction action;
		if(amount < 0) action = mouseActions[codeNeg];
		else action = mouseActions[codePos];
		if(action != null){
			action.press(Math.abs(amount));
			action.release();
		}
	}

}
